#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<assert.h>

#define SIZE 32
#define NUM 1024
#define SEP " "

//保存打散之后的命令行字符串
char *g_argv[SIZE];
//保存完整的命令行字符
char cmd_line[NUM];

//写一个环境变量的buffer,用来测试
char g_myval[64];

#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
#define NONE_REDIR 0

int redir_status = NONE_REDIR;

char *CheckRedir(char *start)
{
    assert(start);
    char *end = start + strlen(start); //ls -a -l\0
    while(end >= start)
    {
        if(*end == '>')
        {
            //ls -a -l>mufile.txt 输出重定向
            //ls -a -l>>mufile.txt 追加重定向
            if(*(end-1) == '>')
            {
                redir_status = APPEND_REDIR;
                *(end-1) = '\0';
                end++;
                break;
            }
            //>
            redir_status = OUTPUT_REDIR;
            *end = '\0';
            end++;
            break;
        }
        else if(*end == '<')
        {
            //ls -a -l<mufile.txt 输入重定向
            //cat < myfile.txt 输入
            redir_status = INPUT_REDIR;
            *end = '\0';
            end++;
            break;
        }
        else 
        {
            end--;
        }   
    }
    if(end >= start)
    {   
        return end;//要打开的文件
    }
    else 
    {
        return NULL;
    }
}

//shell运行原理：通过让子进程执行命令，父进程等待&&解析命令
int main()
{
    extern char**environ;
    //0.命令行解析器一定是一个常驻内存的进程，不退出
    while(1)
    {
        //1.打印出提示信息:[yxj@localhost myshell]#
        printf("[yxj@我的主机 myshell]# ");
        fflush(stdout);
        memset(cmd_line,'\0',sizeof cmd_line);
        //2.获取用户的输入：输入的是各种指令和选项（"ls -a -l -i"）
        //"ls -a -l>log.txt"
        //"ls -a -l>>log.txt"
        //"ls -a -l<log.txt"
        if(fgets(cmd_line,sizeof cmd_line,stdin) == NULL)
        {
            continue;
        }
        //"ls -a -l -i\n\0"
        cmd_line[strlen(cmd_line)-1] = '\0';
        //2.1:分析是否有重定向,"ls -a -l>log.txt" -> "ls -a -l\0log.txt"
        //"ls -a -l -i\n\0"
        char *sep = CheckRedir(cmd_line);

        //printf("echo:%s\n",cmd_line);
        //3.命令行字符串解析："ls -a -l -i" -> "ls" "-a" "-l" "-i"
        g_argv[0] = strtok(cmd_line,SEP);//第一次调用要传入原始字符串
        int index = 1;
        if(strcmp(g_argv[0],"ls") == 0)
        {
            g_argv[index++] = "--color=auto";
        }
        if(strcmp(g_argv[0],"ll") == 0)
        {
            g_argv[0] = "ls";
            g_argv[index++] = "-l";
            g_argv[index++] = "--color=auto";
        }
        while(g_argv[index++] = strtok(NULL,SEP)); //第二次如果还要解析原始字符串，传入NULL
        if(strcmp(g_argv[0],"export") == 0 && g_argv[1] != NULL)
        {
            strcpy(g_myval,g_argv[1]);
           int ret = putenv(g_myval);
           if(ret == 0) printf("%s export success\n",g_argv[1]);
           //for(int i = 0; environ[i]; ++i)
               //printf("%d: %s\n",i,environ[i]);
            continue;
        }
        //for 目的 debug
        //for(index = 0; g_argv[index]; index++)
        //{
           //printf("g_argv[%d]: %s\n",index,g_argv[index]);
        //}
        
        //4.内置命令,让父进程(shell)自己执行的命令,我们叫做内置命令,内建命令
        //内建命令本质就是shell中的一个函数调用
        if(strcmp(g_argv[0],"cd") == 0) //not child execute, father execute
        {
            if(g_argv[1] != NULL) chdir(g_argv[1]); //cd path, cd ...
            continue;
        }
        
        //5.for()
        pid_t id = fork();
        if(id == 0) // child
        {
            if(sep != NULL)
            {
                int fd = -1;
                //说明命令曾经有重定向
                switch(redir_status)
                {
                    case INPUT_REDIR:
                        fd = open(sep,O_RDONLY);
                        dup2(fd,0);
                        break;
                    case OUTPUT_REDIR:
                        fd = open(sep,O_WRONLY | O_TRUNC | O_CREAT, 0666);
                        dup2(fd,1);
                        break;
                    case APPEND_REDIR:
                        fd = open(sep,O_WRONLY | O_APPEND | O_CREAT, 0666);
                        dup2(fd,1);
                        break;
                    default:
                        printf("bug?\n");
                        break;
                }
            }
           // printf("下面功能让子进程进行的\n");
           // printf("child,MYVAL: %s\n",getenv("MYVAL")); 
           // printf("child,PATH: %s\n",getenv("PATH")); 
            //execvpe(g_argv[0],g_argv,environ); //ls -a -l -i
            //程序替换会替换代码和数据吗？环境变量相关的数据，不会被替换，全局变量具有全局属性
            execvp(g_argv[0],g_argv); //ls -a -l -i
            exit(1);
        }
        //father
        int status = 0;
        pid_t ret = waitpid(id,&status,0);
        if(ret > 0)
            printf("exit code:%d\n",WEXITSTATUS(status));
        
    }

}
